﻿//#r "Microsoft.Xna.Framework.dll"

module Generator

    open System
    open Microsoft.Xna.Framework
    open Microsoft.Xna.Framework.Content
    open Microsoft.Xna.Framework.Graphics
    open XNAExtensions

    type ModelInstance = {model:Model; world:Matrix}

    let rec generate_points A B C D (oldPos:Vector3) numPoints =
      seq{
        if numPoints > 0 then
          let newPosx = sin(A * oldPos.Y) - oldPos.Z * cos(B * oldPos.X)
          let newPosz = sin(oldPos.X)
          let newPosy = oldPos.Z * sin(C * oldPos.X) - cos(D * oldPos.Y)
          let newPos = Vector3(newPosx, newPosy, newPosz)
          yield newPos
          yield! generate_points A B C D newPos (numPoints-1)}
 
    let random = Random()

    let rec random_stars numStars min_color max_color = 
      let bounds = BoundingBox(Vector3.Zero, Vector3.One)
      seq{
        if numStars > 0 then
          let color = 
            let a = random.NextDouble() |> float32
            let v = Vector3.Lerp(min_color, max_color, a)
            v * (random.NextDouble() |> float32)
          let random min max = (random.NextDouble() |> float32) * (max-min) + min
          let starx = random bounds.Min.X bounds.Max.X
          let stary = random bounds.Min.Y bounds.Max.Y
          let starz = random bounds.Min.Z bounds.Max.Z
          yield Vector3(starx, stary, starz), color
          yield! random_stars (numStars-1) min_color max_color
      }
 
    let generate_texture A B C D origin eye models numPoints numStars numBackgroundStars (stars_min_color:Color) (stars_max_color:Color) (background_min_color:Color) (background_max_color:Color) graphicsDevice (content:ContentManager) (spriteBatch:SpriteBatch) tex_cube_size =
      let points = generate_points A B C D origin numPoints |> Seq.toList
      let bounds = (BoundingBox.CreateFromPoints(points))  
      let stars = random_stars numStars (stars_min_color.ToVector3()) (stars_max_color.ToVector3()) |> Seq.toList
      let background_stars = random_stars numBackgroundStars (background_min_color.ToVector3()) (background_max_color.ToVector3()) |> Seq.toList
      let points = points |> Seq.map (fun p ->
        let p = bounds.ScaleToUnit p
        let stars_weights = stars |> Seq.map (fun s -> s |> snd, 1.0f / (pow (Vector3.Distance(p, s |> fst)) 3.0f))
        let weights_sum = stars_weights  |> Seq.sumBy (fun (_,w) -> w)
        let stars_weights = stars_weights |> Seq.map (fun (s,w) -> (s,w/weights_sum))
        let color = stars_weights |> Seq.map (fun (c,w) -> c * w) |> Seq.sum
        in p,color)
      let points = points |> Seq.toList 

      do Console.WriteLine "done generating points, now creating cube map"

      let to_triangles size (pc:VertexPositionColor) =
        let tri_uv = seq{
                         yield Vector2(0.0f, 0.0f)
                         yield Vector2(0.0f, 1.0f)
                         yield Vector2(1.0f, 0.0f)
                         yield Vector2(1.0f, 0.0f)
                         yield Vector2(0.0f, 1.0f)
                         yield Vector2(1.0f, 1.0f)
                      }
        let mk_tris (xv:Vector3) (yv:Vector3) = tri_uv |> Seq.map (fun uv -> VertexPositionColorTexture(pc.Position + uv.X * xv * size + uv.Y * yv * size, pc.Color, uv))
        seq{
          yield! mk_tris Vector3.Right Vector3.Up
          yield! mk_tris Vector3.Right Vector3.Forward 
          yield! mk_tris Vector3.Forward Vector3.Up 
        }
     
      let big_stars = 
        let data = stars |> Seq.map (fun (p,c) -> VertexPositionColor(p,Color(Vector3.Normalize(c))) |> to_triangles (random.NextFloat 0.05f)) |> Seq.concat |> Seq.toArray
        let vb = new VertexBuffer(graphicsDevice, typeof<VertexPositionColorTexture>, data.Length, BufferUsage.None)
        do vb.SetData(data)
        vb
      let background_stars = 
        let data = background_stars |> Seq.map (fun (p,c) -> VertexPositionColor(p,Color(Vector3.Normalize(c))) |> to_triangles (random.NextFloat 0.005f)) |> Seq.concat |> Seq.toArray
        let vb = new VertexBuffer(graphicsDevice, typeof<VertexPositionColorTexture>, data.Length, BufferUsage.None)
        do vb.SetData(data)
        vb
      let vertices = 
        let data = points |> Seq.map (fun (p,c) -> VertexPositionColor(p,Color(c)) |> to_triangles (random.NextFloat 0.001f)) |> Seq.concat |> Seq.toArray
        let vb = new VertexBuffer(graphicsDevice, typeof<VertexPositionColorTexture>, data.Length, BufferUsage.None)
        do vb.SetData(data)
        vb  
      let bfx = new BasicEffect(graphicsDevice)
      let tex_cube = [|
                        new RenderTarget2D(graphicsDevice, tex_cube_size, tex_cube_size)
                        new RenderTarget2D(graphicsDevice, tex_cube_size, tex_cube_size)
                        new RenderTarget2D(graphicsDevice, tex_cube_size, tex_cube_size)
                        new RenderTarget2D(graphicsDevice, tex_cube_size, tex_cube_size)
                        new RenderTarget2D(graphicsDevice, tex_cube_size, tex_cube_size)
                        new RenderTarget2D(graphicsDevice, tex_cube_size, tex_cube_size)
                     |]


      let faces = [
          ((fun eye -> Matrix.CreateLookAt(eye, eye + Vector3.Forward, Vector3.Up)), CubeMapFace.PositiveZ);
          ((fun eye -> Matrix.CreateLookAt(eye, eye + Vector3.Backward, Vector3.Up)), CubeMapFace.NegativeZ);
          ((fun eye -> Matrix.CreateLookAt(eye, eye + Vector3.Right, Vector3.Up)), CubeMapFace.PositiveX);
          ((fun eye -> Matrix.CreateLookAt(eye, eye + Vector3.Left, Vector3.Up)), CubeMapFace.NegativeX);
          ((fun eye -> Matrix.CreateLookAt(eye, eye + Vector3.Up, Vector3.Backward)), CubeMapFace.PositiveY);
          ((fun eye -> Matrix.CreateLookAt(eye, eye + Vector3.Down, Vector3.Forward)), CubeMapFace.NegativeY);
        ]

      let proj = Matrix.CreateOrthographic(1.0f, 1.0f, 0.0001f, 10.00001f)
      do bfx.Projection <- proj
      do bfx.World <- Matrix.Identity
      do bfx.VertexColorEnabled <- true
  
      let star1_tex = content.Load "star1"
      let star2_tex = content.Load "star2"
      let plasma = content.Load "plasma"
  
      do bfx.TextureEnabled <- true

      let draw_face (view,cube_face) =
        graphicsDevice.SetRenderTarget(tex_cube.[cube_face |> int])
        do graphicsDevice.Clear(Color.Black)

        do graphicsDevice.BlendState <- BlendState.Additive
        do graphicsDevice.DepthStencilState <- DepthStencilState.DepthRead
        do graphicsDevice.RasterizerState <- RasterizerState.CullNone
        let draw vertices tex =
          do bfx.Texture <- tex
          do graphicsDevice.SetVertexBuffer(vertices)
          do (bfx.CurrentTechnique.Passes.[0]).Apply()
          let num_primitives = vertices.VertexCount / 3
          let num_batches = num_primitives / 65535
          for i in 0..num_batches do
            do graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, i * 65535, 65535)
          if num_primitives % 65535 > 0 then
            do graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, num_batches * 65535, num_primitives % 65535)
        do bfx.View <- view (Vector3(0.5f))
        do draw background_stars star1_tex
        do bfx.View <- view eye
        do draw big_stars star2_tex
        do draw vertices plasma

        let proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, 1.0f, 0.00001f, 10.00001f)
        do graphicsDevice.BlendState <- BlendState.Opaque
        do graphicsDevice.DepthStencilState <- DepthStencilState.Default
        do graphicsDevice.RasterizerState <- RasterizerState.CullNone
        for m in models do
          do m.model.Draw(m.world, view eye, proj)

      do faces |> Seq.iter draw_face

      graphicsDevice.SetRenderTarget(null)
      tex_cube |> Array.map (fun rt -> rt :> Texture2D)
  
  
    let generate_texture_nice eye models numPoints numStars numBackgroundStars (stars_min_color:Color) (stars_max_color:Color) (background_min_color:Color) (background_max_color:Color) graphicsDevice content spriteBatch tex_cube_size = 
    //  let A = -0.9629629f
    //  let B = 2.791139f
    //  let C = 1.85185185f
    //  let D = 1.5f
      let A = -0.9029629f
      let B = 2.291139f
      let C = 2.15185185f
      let D = 1.5f
      in generate_texture A B C D Vector3.Zero eye models numPoints numStars numBackgroundStars stars_min_color stars_max_color background_min_color background_max_color graphicsDevice content spriteBatch tex_cube_size
